//! Internal implementation details of `cpuindex`.
//!
//! Do not use this crate directly.

extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
    parse::{self},
    parse_macro_input,
    spanned::Spanned,
    Ident, ItemFn, ReturnType, Visibility,
};

#[proc_macro_attribute]
pub fn ffi_nr_cpus(args: TokenStream, input: TokenStream) -> TokenStream {
    let f = parse_macro_input!(input as ItemFn);

    // check the function signature
    let valid_signature = f.sig.constness.is_none()
        && f.vis == Visibility::Inherited
        && f.sig.abi.is_none()
        && f.sig.generics.params.is_empty()
        && f.sig.generics.where_clause.is_none()
        && f.sig.variadic.is_none()
        && f.sig.inputs.len() == 1
        && matches!(f.sig.output, ReturnType::Default);

    if !valid_signature {
        return parse::Error::new(
            f.span(),
            "`#[ffi_nr_cpus]` function must have signature `[unsafe] fn(usize)`",
        )
        .to_compile_error()
        .into();
    }

    if !args.is_empty() {
        return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
            .to_compile_error()
            .into();
    }

    let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
    let ident = &f.sig.ident;

    quote!(
        #[doc(hidden)]
        #[export_name = "__ffi_nr_cpus"]
        pub unsafe extern "C" fn #tramp_ident(nr: usize) {
            #ident(nr)
        }

        #f
    )
    .into()
}
